import { getType } from '@/library/libs/get-type'

const typeArrayReg = /^\[object (?:Float(?:32|64)|(?:Int|Uint)(?:8|16|32)|Uint8Clamped)Array\]$/

const { hasOwnProperty } = Object.prototype

export const isNull = (value: unknown): boolean => value === null

export const isUndefined = (value: unknown): boolean => value === undefined

/**
 * @description 判断值是否为`null`或`undefined`
 * @param {*} value 值
 * @returns {boolean}
 * @example
 * -----------------------
 * isNil(null)
 * // => true
 *
 * isNil(void 0)
 * // => true
 *
 * isNil(NaN)
 * // => false
 */
export const isNil = (value: unknown): boolean => value == null

/**
 * @description 值是否为字符串类型. `string` primitive or object.
 * @param {*} value 值
 * @returns {boolean}
 * @example
 * -----------------------
 * isString('abc')
 * // => true
 * -----------------------
 * isString(new String('abc'))
 * // => true
 */
export const isString = (value: unknown): boolean => typeof value === 'string' || getType(value) === 'string'

/**
 * @description 值是否为数值类型. `number` primitive or object.
 * @param {*} value 值
 * @returns {boolean}
 * @example
 * -----------------------
 * isNumber(1)
 * // => true
 * -----------------------
 * isNumber(new Number(1))
 * // => true
 * -----------------------
 * isNumber(Number.MIN_VALUE)
 * // => true
 * -----------------------
 * isNumber(Numner.POSITIVE_INFINITY)
 * // => true
 * -----------------------
 * isNumber('1')
 * // => false
 */
export const isNumber = (value: unknown): boolean => typeof value === 'number' || getType(value) === 'number'

/**
 * @description 值是否为字布尔类型. `boolean` primitive or object.
 * @param {*} value 值
 * @returns {boolean}
 * @example
 * -----------------------
 * isBoolean(true)
 * // => true
 * -----------------------
 * isBoolean(false)
 * // => true
 * -----------------------
 * isBoolean(null)
 * // => false
 */
export const isBoolean = (value: unknown): boolean => typeof value === 'boolean' || getType(value) === 'boolean'

/**
 * @description 值是否为字符串类型. `symbol` primitive
 * @param {*} value 值
 * @returns {boolean}
 * @example
 * -----------------------
 * isSymbol(Symbol('foo'))
 * // => true
 * -----------------------
 * isSymbol(Symbol.iterator)
 * // => true
 * -----------------------
 * isSymbol('symbol')
 * // => false
 */
export const isSymbol = (value: unknown): boolean => typeof value === 'symbol' || getType(value) === 'symbol'

export const isArray = (value: unknown): boolean => (Array.isArray ? Array.isArray(value) : getType(value) === 'array')

export const isFunction = (value: unknown): boolean => getType(value) === 'function'

export const isSet = (value: unknown): boolean => getType(value) === 'set'

export const isMap = (value: unknown): boolean => getType(value) === 'map'

/**
 * @description 是否是object类型
 * @param {any} value
 * @returns {boolean}
 * @example
 * --------------------------------
 * isObject({})
 * // => true
 * --------------------------------
 * isObject([1, 2, 3])
 * // => true
 * --------------------------------
 * isObject(Function)
 * // => true
 *
 * isObject(null)
 * // => false
 */
export const isObject = (value: unknown): boolean => {
  const type = typeof value
  return value != null && (type === 'object' || type === 'function')
}

/**
 * @description 是否为类对象
 * @param {*} value 需判定的值
 * @returns {boolean} `true or false`
 * @example
 * ----------------------
 * isObjectLike({})
 * // => true
 * ----------------------
 * isObjectLike([1, 2, 3])
 * // => true
 * ----------------------
 * isObjectLike(Function)
 * // => false
 * ----------------------
 * isObjectLike(null)
 * // => false
 */
export const isObjectLike = (value: unknown): boolean => typeof value === 'object' && value !== null

export const isArguments = (value: unknown): boolean => getType(value) === 'arguments'

export const isRegExp = (value: unknown): boolean => getType(value) === 'regExp'

export const isDate = (value: unknown): boolean => getType(value) === 'date'

export const isPromise = (value: unknown): boolean => (!!value && typeof value === 'object' && typeof (value as any).then === 'function')

export const isElement = (value: unknown): boolean => getType(value) === 'element' && (value as Element).nodeType === 1

export const isWindow = (value: unknown): boolean => getType(value) === 'window' && (value as Window).window && 'document' in (value as Window)

/**
 * @description 是否为原型对象
 * @param {*} value 值
 * @returns {boolean}
 */
export const isPrototype = (value: unknown): boolean => {
  const ctor = value && value.constructor
  const proto = (typeof ctor === 'function' && ctor.prototype) || Object.prototype
  return value === proto
}

/**
 * @description 是否为TypedArray类型的对象
 * @param {*} value 需判定的值
 * @returns {boolean}
 * @example
 * --------------------
 * isTypedArray(new Uint8Array(10))
 * // => true
 * --------------------
 * isTypedArray([])
 * // => false
 */
export const isTypedArray = (value: unknown): boolean => isObjectLike(value) && typeArrayReg.test(Object.prototype.toString.call(value))

/**
 * @description 是否为一个空对象
 * @param {object} value
 * @returns {boolean}
 * @example
 * ----------------------
 * isEmptyObject(null)
 * // => false
 * ----------------------
 * isEmptyObject([])
 * // => false
 * ----------------------
 * isEmptyObject({a: 1})
 * // => false
 * ----------------------
 * isEmptyObject({})
 * // => true
 */
export const isEmptyObject = (value: unknown): boolean => {
  const type = getType(value)

  if (type !== 'object') return false

  return Object.keys(value as any).length === 0
}

/**
 * @description 是否为一个非空对象
 * @param {object} value
 * @returns {boolean}
 * @example
 * ----------------------
 * isEmptyObject(null)
 * // => false
 * ----------------------
 * isEmptyObject([])
 * // => false
 * ----------------------
 * isEmptyObject({a: 1})
 * // => true
 * ----------------------
 * isEmptyObject({})
 * // => false
 */
export const isNotEmptyObject = (value: unknown): boolean => {
  const type = getType(value)

  if (type !== 'object') return false

  return Object.keys(value as any).length !== 0
}

/**
 * @description `value` 是否是普通对象。该对象由 `Object` 构造函数创建 或 对象字面量 或 `[[Prototype]]` 为 `null`。
 * @param {*} value 值
 * @returns {boolean}
 * @example
 * -------------------------
 * isPlainObject(null)
 * // => false
 * -------------------------
 * isPlainObject([])
 * // => false
 * -------------------------
 * function Abc(a) {
 *   this.a = a
 * }
 * isPlainObject(new Abc(1))
 * // => false
 * -------------------------
 * isPlainObject({ a: 1 })
 * // => true
 * -------------------------
 * isPlainObject(Object.create(null))
 * // => true
 */
export const isPlainObject = (value: unknown): boolean => {
  if (getType(value) !== 'object') return false

  const proto = Object.getPrototypeOf(value)
  if (!proto) return true // Case: Object.create(null)
  const ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor
  // hasOwn.toString 调用的是 Function.prototype.toString , 返回构造函数的源码字符串. 作用：区分自定义构造函数和Object构造函数
  return typeof ctor === 'function' && hasOwnProperty.toString.call(ctor) === hasOwnProperty.toString.call(Object)
}

export const isObjectType = (value: unknown): boolean => getType(value) === 'object'

/**
 * @description 是否为类数组对象（包含数组、arguments、字符串、Dom中的类对象）
 * @param {*} value
 * @returns {boolean}
 * @example
 * -----------------------
 * isArrayLike('abc')
 * // => true
 * -----------------------
 * isArrayLike([])
 * // => true
 * -----------------------
 * isArrayLike(new Uint8Array(10))
 * // => true
 * -----------------------
 * function fn() {
 *  return isArrayLike(arguments)
 * }
 * // => true
 * -----------------------
 * const obj = {a: 1, length: 2}
 * isArrayLike(obj)
 * // => true
 * -----------------------
 * isArrayLike(document.body.children)
 * // => true
 */
export const isArrayLike = (value: unknown): boolean => {
  type InnerValue = {
    length?: number
    [key: string]: unknown
  }
  if (isNil(value)) return false
  if (isString(value)) return true
  if (typeof value !== 'object') return false
  if (isWindow(value)) return false
  const innerValue = value as InnerValue
  if (
    Number.isFinite(innerValue.length)
    && innerValue.length! >= 0
    && innerValue.length! === Math.floor(innerValue.length!)
    && innerValue.length! < 2 ** 32
  ) return true

  return false
}

/**
 * @description 是否为一个空对象，空数组（包含类数组）、空集合(set)、空映射(map)、空字符串。
 * @param {*} value
 * @returns {boolean}
 * @example
 * --------------------
 * isEmpty(null)
 * // => true
 * --------------------
 * isEmpty()
 * // => true
 * --------------------
 * isEmpty(true)
 * // => true
 * --------------------
 * isEmpty(1)
 * // => true
 * --------------------
 * isEmpty([1, 2, 3])
 * // => false
 * --------------------
 * isEmpty('')
 * // => true
 * --------------------
 * isEmpty('123')
 * // => false
 * --------------------
 * isEmpty({})
 * // => true
 * --------------------
 * isEmpty({ a: 1 })
 * // => false
 */
export const isEmpty = (value: unknown): boolean => {
  if (isNil(value)) return true

  const type = getType(value)

  if (type === 'set' || type === 'map') return !(value as any).size

  if (isArrayLike(value)) return !(value as any).length

  if (isPrototype(value)) return !Object.keys(value!).length

  if (type === 'object') {
    for (const key in value!) {
      if (hasOwnProperty.call(value, key)) {
        return false
      }
    }
  }

  return true
}
